#include "packet.h"
#include "../packet_headers/iphdr.h"
#include "../packet_headers/tcphdr.h"
#include "../packet_headers/udphdr.h"
#include "../packet_headers/icmphdr.h"
#include "../packet_headers/checksum.h"
#include "../packet_headers/arphdr.h"
#include "../packet_headers/etherhdr.h"
#include "info.h"
#include "hw_translate.h"

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>

#define TTL_DEFAULT 255
#define TCP_WINDOW_SIZE 5000

byte * ethernet_frame(const char * srcaddr, const char * destaddr,
		unsigned short type, const char * data, unsigned int len) {
	byte * datagram = new byte[sizeof(struct etherheader)+len];
	struct etherheader * etherhdr = (struct etherheader *) datagram;
	memcpy(&(etherhdr->ether_dhost), destaddr, 6);
	memcpy(&(etherhdr->ether_shost), srcaddr, 6);
	etherhdr->ether_type = type;
	memcpy(datagram+sizeof(struct etherheader), data, len);
	return datagram;
}

byte * ip_packet(const char * _src, const char * _dest,
		byte _TOS, unsigned int _id, byte * _data,
		unsigned char _proto, unsigned int _checksum,
		unsigned int _len) {

	byte * datagram = new byte[sizeof(struct ipheader) + _len];
	struct ipheader * iphdr = (struct ipheader*) datagram;
	memset(datagram, 0, sizeof(struct ipheader) + _len);

	// Fill in IP header
	iphdr->header_length = 5;
	iphdr->version = IP_VERSION;
	iphdr->src = inet_addr(_src);
	iphdr->dst = inet_addr(_dest);
	iphdr->TOS = _TOS;
	iphdr->proto = _proto;
	iphdr->TTL = TTL_DEFAULT;
	iphdr->id = htons(_id);
	iphdr->offset = 0x40;
	iphdr->total_length = htons(sizeof(struct ipheader) + _len);
	iphdr->checksum = 0;

	// Copy in the payload
	memcpy(datagram + sizeof(struct ipheader), _data, _len);

	// Calculate the checksum
	iphdr->checksum = htons(checksum(datagram,20));

	// Virtual corruption
	iphdr->checksum = iphdr->checksum ^ _checksum;

	unsigned int len = _len + sizeof(struct ipheader);
	byte * frame = ethernet_frame(get_local_hw(), get_gateway_hw(), htons(0x0800), (char*)datagram, len);

	return frame;
}

byte * tcp_packet(const char * _src, const char * _dest,
		byte _TOS, unsigned int _id,
		char * _data, unsigned int _len,
		unsigned short _srcport, unsigned short _destport,
		unsigned int _seq, unsigned int _ack, byte _flags,
		unsigned int _checksum) {

	byte segment[sizeof(struct tcpheader) + _len];
	memset(segment, 0, sizeof(struct tcpheader) + _len);

	// Fill in the TCP header
	struct tcpheader * tcphdr = (struct tcpheader*) segment;
	tcphdr->ack_num = _ack;
	tcphdr->checksum = 0;
	tcphdr->dest_port = htons(_destport);
	tcphdr->src_port = htons(_srcport);
	tcphdr->flags = _flags;
	tcphdr->offset = 5;
	tcphdr->seq_num = _seq;
	tcphdr->th_x2 = 0;
	tcphdr->urgent_pointer = 0;
	tcphdr->window = htons(5000);

	// Copy in the data
	memcpy(segment+sizeof(struct tcpheader), _data, _len);

	//Calculate the checksum (TCP)
	/*tcphdr->checksum =
			checksum(segment,
					(sizeof(struct tcpheader) + _len));*/

	unsigned int len = sizeof(struct tcpheader) + _len;
	byte * packet = ip_packet(_src, _dest, _TOS, _id, segment,
			TCP_PROTO, _checksum, len);
	return packet;
}

byte * udp_packet(const char * _src, const char * _dest,
		byte _TOS, unsigned int _id,
		char * _data, unsigned int _len,
		unsigned short _srcport, unsigned short _destport,
		unsigned int _checksum) {

	byte segment[sizeof(struct udpheader) + _len];
	memset(segment, 0, sizeof(struct udpheader)+_len);

	// Fill the UDP header
	struct udpheader * udphdr = (struct udpheader*) segment;
	udphdr->src_port = htons(_srcport);
	udphdr->dest_port = htons(_destport);
	udphdr->length = htons(sizeof(struct udpheader) + _len);
	udphdr->checksum = 0;

	// Copy in the data
	memcpy(segment+sizeof(struct udpheader), _data, _len);

	// Calculate the checksum (UDP)
	/*udphdr->checksum =
			checksum(segment,
			(sizeof(struct udpheader) + _len));*/

	byte * packet = ip_packet(_src, _dest, _TOS, _id, segment,
			UDP_PROTO, _checksum, sizeof(struct udpheader) + _len);
	return packet;
}

byte * icmp_packet(const char * _src, const char * _dest,
		byte _TOS, unsigned int _id,
		unsigned char icmp_type, unsigned char icmp_code,
		unsigned short int icmp_id, unsigned short int icmp_seq,
		unsigned int _checksum) {

	byte segment[sizeof(struct icmpheader)];
	memset(segment, 0, sizeof(struct icmpheader));

	// Fill ICMP header
	struct icmpheader * icmphdr = (struct icmpheader*) segment;
	icmphdr->id = htons(_id);
	icmphdr->code = icmp_code;
	icmphdr->seq = htons(icmp_seq);
	icmphdr->type = icmp_type;
	icmphdr->checksum = htons(checksum(segment, sizeof(struct icmpheader)));

	byte * packet = ip_packet(_src, _dest, _TOS, _id, segment,
			ICMP_PROTO, _checksum, sizeof(struct icmpheader));
	return packet;
}

byte * arp_packet(unsigned short operation, const char * src_ip, char * dest_hw, const char * dest_ip) {
	struct arp_packet * arp = (struct arp_packet*) new char[sizeof(struct arp_packet)];
	memset(arp, 0, sizeof(struct arp_packet));
	// Default information
	arp->frame_type = htons(ARP_FRAME_TYPE);
	arp->hw_type = htons(ETHER_HW_TYPE);
	arp->prot_type = htons(IP_PROTO_TYPE);
	arp->hw_addr_size = ETH_HW_ADDR_LEN;
	arp->prot_addr_size = IP_ADDR_LEN;

	arp->op=htons(operation);
	const char * src_mac = get_local_hw();
	char * dest_mac = dest_hw; //get_hw_addr(dest_hw);
	in_addr_t src_addr = inet_addr(src_ip);
	in_addr_t dest_addr = inet_addr(dest_ip);
	memcpy(&(arp->sndr_hw_addr), src_mac, ETH_HW_ADDR_LEN);
	memcpy(&(arp->rcpt_hw_addr), dest_mac, ETH_HW_ADDR_LEN);
	memcpy(&(arp->sndr_ip_addr), &src_addr, IP_ADDR_LEN);
	memcpy(&(arp->rcpt_ip_addr), &dest_addr, IP_ADDR_LEN);
	memcpy(&(arp->src_hw_addr), src_mac, ETH_HW_ADDR_LEN);
	memcpy(&(arp->targ_hw_addr), dest_mac, ETH_HW_ADDR_LEN);

	return (byte *) arp;
}
